home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / share / pyshared / xdg / Menu.py < prev    next >
Encoding:
Python Source  |  2009-10-28  |  35.5 KB  |  1,075 lines

  1. """
  2. Implementation of the XDG Menu Specification Version 1.0.draft-1
  3. http://standards.freedesktop.org/menu-spec/
  4. """
  5.  
  6. from __future__ import generators
  7. import locale, os, xml.dom.minidom
  8.  
  9. from xdg.BaseDirectory import *
  10. from xdg.DesktopEntry import *
  11. from xdg.Exceptions import *
  12.  
  13. import xdg.Locale
  14. import xdg.Config
  15.  
  16. ELEMENT_NODE = xml.dom.Node.ELEMENT_NODE
  17.  
  18. # for python <= 2.3
  19. try:
  20.     reversed = reversed
  21. except NameError:
  22.     def reversed(x):
  23.         return x[::-1]
  24.  
  25. class Menu:
  26.     def __init__(self):
  27.         # Public stuff
  28.         self.Name = ""
  29.         self.Directory = None
  30.         self.Entries = []
  31.         self.Doc = ""
  32.         self.Filename = ""
  33.         self.Depth = 0
  34.         self.Parent = None
  35.         self.NotInXml = False
  36.  
  37.         # Can be one of Deleted/NoDisplay/Hidden/Empty/NotShowIn or True
  38.         self.Show = True
  39.         self.Visible = 0
  40.  
  41.         # Private stuff, only needed for parsing
  42.         self.AppDirs = []
  43.         self.DefaultLayout = None
  44.         self.Deleted = "notset"
  45.         self.Directories = []
  46.         self.DirectoryDirs = []
  47.         self.Layout = None
  48.         self.MenuEntries = []
  49.         self.Moves = []
  50.         self.OnlyUnallocated = "notset"
  51.         self.Rules = []
  52.         self.Submenus = []
  53.  
  54.     def __str__(self):
  55.         return self.Name
  56.  
  57.     def __add__(self, other):
  58.         for dir in other.AppDirs:
  59.             self.AppDirs.append(dir)
  60.  
  61.         for dir in other.DirectoryDirs:
  62.             self.DirectoryDirs.append(dir)
  63.  
  64.         for directory in other.Directories:
  65.             self.Directories.append(directory)
  66.  
  67.         if other.Deleted != "notset":
  68.             self.Deleted = other.Deleted
  69.  
  70.         if other.OnlyUnallocated != "notset":
  71.             self.OnlyUnallocated = other.OnlyUnallocated
  72.  
  73.         if other.Layout:
  74.             self.Layout = other.Layout
  75.  
  76.         if other.DefaultLayout:
  77.             self.DefaultLayout = other.DefaultLayout
  78.  
  79.         for rule in other.Rules:
  80.             self.Rules.append(rule)
  81.  
  82.         for move in other.Moves:
  83.             self.Moves.append(move)
  84.  
  85.         for submenu in other.Submenus:
  86.             self.addSubmenu(submenu)
  87.  
  88.         return self
  89.  
  90.     # FIXME: Performance: cache getName()
  91.     def __cmp__(self, other):
  92.         return locale.strcoll(self.getName(), other.getName())
  93.  
  94.     def __eq__(self, other):
  95.         if self.Name == str(other):
  96.             return True
  97.         else:
  98.             return False
  99.  
  100.     """ PUBLIC STUFF """
  101.     def getEntries(self, hidden=False):
  102.         for entry in self.Entries:
  103.             if hidden == True:
  104.                 yield entry
  105.             elif entry.Show == True:
  106.                 yield entry
  107.  
  108.     # FIXME: Add searchEntry/seaqrchMenu function
  109.     # search for name/comment/genericname/desktopfileide
  110.     # return multiple items
  111.  
  112.     def getMenuEntry(self, desktopfileid, deep = False):
  113.         for menuentry in self.MenuEntries:
  114.             if menuentry.DesktopFileID == desktopfileid:
  115.                 return menuentry
  116.         if deep == True:
  117.             for submenu in self.Submenus:
  118.                 submenu.getMenuEntry(desktopfileid, deep)
  119.  
  120.     def getMenu(self, path):
  121.         array = path.split("/", 1)
  122.         for submenu in self.Submenus:
  123.             if submenu.Name == array[0]:
  124.                 if len(array) > 1:
  125.                     return submenu.getMenu(array[1])
  126.                 else:
  127.                     return submenu
  128.  
  129.     def getPath(self, org=False, toplevel=False):
  130.         parent = self
  131.         names=[]
  132.         while 1:
  133.             if org:
  134.                 names.append(parent.Name)
  135.             else:
  136.                 names.append(parent.getName())
  137.             if parent.Depth > 0:
  138.                 parent = parent.Parent
  139.             else:
  140.                 break
  141.         names.reverse()
  142.         path = ""
  143.         if toplevel == False:
  144.             names.pop(0)
  145.         for name in names:
  146.             path = os.path.join(path, name)
  147.         return path
  148.  
  149.     def getName(self):
  150.         try:
  151.             return self.Directory.DesktopEntry.getName()
  152.         except AttributeError:
  153.             return self.Name
  154.  
  155.     def getGenericName(self):
  156.         try:
  157.             return self.Directory.DesktopEntry.getGenericName()
  158.         except AttributeError:
  159.             return ""
  160.  
  161.     def getComment(self):
  162.         try:
  163.             return self.Directory.DesktopEntry.getComment()
  164.         except AttributeError:
  165.             return ""
  166.  
  167.     def getIcon(self):
  168.         try:
  169.             return self.Directory.DesktopEntry.getIcon()
  170.         except AttributeError:
  171.             return ""
  172.  
  173.     """ PRIVATE STUFF """
  174.     def addSubmenu(self, newmenu):
  175.         for submenu in self.Submenus:
  176.             if submenu == newmenu:
  177.                 submenu += newmenu
  178.                 break
  179.         else:
  180.             self.Submenus.append(newmenu)
  181.             newmenu.Parent = self
  182.             newmenu.Depth = self.Depth + 1
  183.  
  184. class Move:
  185.     "A move operation"
  186.     def __init__(self, node=None):
  187.         if node:
  188.             self.parseNode(node)
  189.         else:
  190.             self.Old = ""
  191.             self.New = ""
  192.  
  193.     def __cmp__(self, other):
  194.         return cmp(self.Old, other.Old)
  195.  
  196.     def parseNode(self, node):
  197.         for child in node.childNodes:
  198.             if child.nodeType == ELEMENT_NODE:
  199.                 if child.tagName == "Old":
  200.                     try:
  201.                         self.parseOld(child.childNodes[0].nodeValue)
  202.                     except IndexError:
  203.                         raise ValidationError('Old cannot be empty', '??')                                            
  204.                 elif child.tagName == "New":
  205.                     try:
  206.                         self.parseNew(child.childNodes[0].nodeValue)
  207.                     except IndexError:
  208.                         raise ValidationError('New cannot be empty', '??')                                            
  209.  
  210.     def parseOld(self, value):
  211.         self.Old = value
  212.     def parseNew(self, value):
  213.         self.New = value
  214.  
  215.  
  216. class Layout:
  217.     "Menu Layout class"
  218.     def __init__(self, node=None):
  219.         self.order = []
  220.         if node:
  221.             self.show_empty = node.getAttribute("show_empty") or "false"
  222.             self.inline = node.getAttribute("inline") or "false"
  223.             self.inline_limit = node.getAttribute("inline_limit") or 4
  224.             self.inline_header = node.getAttribute("inline_header") or "true"
  225.             self.inline_alias = node.getAttribute("inline_alias") or "false"
  226.             self.inline_limit = int(self.inline_limit)
  227.             self.parseNode(node)
  228.         else:
  229.             self.show_empty = "false"
  230.             self.inline = "false"
  231.             self.inline_limit = 4
  232.             self.inline_header = "true"
  233.             self.inline_alias = "false"
  234.             self.order.append(["Merge", "menus"])
  235.             self.order.append(["Merge", "files"])
  236.  
  237.     def parseNode(self, node):
  238.         for child in node.childNodes:
  239.             if child.nodeType == ELEMENT_NODE:
  240.                 if child.tagName == "Menuname":
  241.                     try:
  242.                         self.parseMenuname(
  243.                             child.childNodes[0].nodeValue,
  244.                             child.getAttribute("show_empty") or "false",
  245.                             child.getAttribute("inline") or "false",
  246.                             child.getAttribute("inline_limit") or 4,
  247.                             child.getAttribute("inline_header") or "true",
  248.                             child.getAttribute("inline_alias") or "false" )
  249.                     except IndexError:
  250.                         raise ValidationError('Menuname cannot be empty', "")
  251.                 elif child.tagName == "Separator":
  252.                     self.parseSeparator()
  253.                 elif child.tagName == "Filename":
  254.                     try:
  255.                         self.parseFilename(child.childNodes[0].nodeValue)
  256.                     except IndexError:
  257.                         raise ValidationError('Filename cannot be empty', "")
  258.                 elif child.tagName == "Merge":
  259.                     self.parseMerge(child.getAttribute("type") or "all")
  260.  
  261.     def parseMenuname(self, value, empty="false", inline="false", inline_limit=4, inline_header="true", inline_alias="false"):
  262.         self.order.append(["Menuname", value, empty, inline, inline_limit, inline_header, inline_alias])
  263.         self.order[-1][4] = int(self.order[-1][4])
  264.  
  265.     def parseSeparator(self):
  266.         self.order.append(["Separator"])
  267.  
  268.     def parseFilename(self, value):
  269.         self.order.append(["Filename", value])
  270.  
  271.     def parseMerge(self, type="all"):
  272.         self.order.append(["Merge", type])
  273.  
  274.  
  275. class Rule:
  276.     "Inlcude / Exclude Rules Class"
  277.     def __init__(self, type, node=None):
  278.         # Type is Include or Exclude
  279.         self.Type = type
  280.         # Rule is a python expression
  281.         self.Rule = ""
  282.  
  283.         # Private attributes, only needed for parsing
  284.         self.Depth = 0
  285.         self.Expr = [ "or" ]
  286.         self.New = True
  287.  
  288.         # Begin parsing
  289.         if node:
  290.             self.parseNode(node)
  291.             self.compile()
  292.  
  293.     def __str__(self):
  294.         return self.Rule
  295.  
  296.     def compile(self):
  297.         exec("""
  298. def do(menuentries, type, run):
  299.     for menuentry in menuentries:
  300.         if run == 2 and ( menuentry.MatchedInclude == True \
  301.         or menuentry.Allocated == True ):
  302.             continue
  303.         elif %s:
  304.             if type == "Include":
  305.                 menuentry.Add = True
  306.                 menuentry.MatchedInclude = True
  307.             else:
  308.                 menuentry.Add = False
  309.     return menuentries
  310. """ % self.Rule) in self.__dict__
  311.  
  312.     def parseNode(self, node):
  313.         for child in node.childNodes:
  314.             if child.nodeType == ELEMENT_NODE:
  315.                 if child.tagName == 'Filename':
  316.                     try:
  317.                         self.parseFilename(child.childNodes[0].nodeValue)
  318.                     except IndexError:
  319.                         raise ValidationError('Filename cannot be empty', "???")
  320.                 elif child.tagName == 'Category':
  321.                     try:
  322.                         self.parseCategory(child.childNodes[0].nodeValue)
  323.                     except IndexError:
  324.                         raise ValidationError('Category cannot be empty', "???")
  325.                 elif child.tagName == 'All':
  326.                     self.parseAll()
  327.                 elif child.tagName == 'And':
  328.                     self.parseAnd(child)
  329.                 elif child.tagName == 'Or':
  330.                     self.parseOr(child)
  331.                 elif child.tagName == 'Not':
  332.                     self.parseNot(child)
  333.  
  334.     def parseNew(self, set=True):
  335.         if not self.New:
  336.             self.Rule += " " + self.Expr[self.Depth] + " "
  337.         if not set:
  338.             self.New = True
  339.         elif set:
  340.             self.New = False
  341.  
  342.     def parseFilename(self, value):
  343.         self.parseNew()
  344.         self.Rule += "menuentry.DesktopFileID == '%s'" % value.strip().replace("\\", r"\\").replace("'", r"\'")
  345.  
  346.     def parseCategory(self, value):
  347.         self.parseNew()
  348.         self.Rule += "'%s' in menuentry.Categories" % value.strip()
  349.  
  350.     def parseAll(self):
  351.         self.parseNew()
  352.         self.Rule += "True"
  353.  
  354.     def parseAnd(self, node):
  355.         self.parseNew(False)
  356.         self.Rule += "("
  357.         self.Depth += 1
  358.         self.Expr.append("and")
  359.         self.parseNode(node)
  360.         self.Depth -= 1
  361.         self.Expr.pop()
  362.         self.Rule += ")"
  363.  
  364.     def parseOr(self, node):
  365.         self.parseNew(False)
  366.         self.Rule += "("
  367.         self.Depth += 1
  368.         self.Expr.append("or")
  369.         self.parseNode(node)
  370.         self.Depth -= 1
  371.         self.Expr.pop()
  372.         self.Rule += ")"
  373.  
  374.     def parseNot(self, node):
  375.         self.parseNew(False)
  376.         self.Rule += "not ("
  377.         self.Depth += 1
  378.         self.Expr.append("or")
  379.         self.parseNode(node)
  380.         self.Depth -= 1
  381.         self.Expr.pop()
  382.         self.Rule += ")"
  383.  
  384.  
  385. class MenuEntry:
  386.     "Wrapper for 'Menu Style' Desktop Entries"
  387.     def __init__(self, filename, dir="", prefix=""):
  388.         # Create entry
  389.         self.DesktopEntry = DesktopEntry(os.path.join(dir,filename))
  390.         self.setAttributes(filename, dir, prefix)
  391.  
  392.         # Can be one of Deleted/Hidden/Empty/NotShowIn/NoExec or True
  393.         self.Show = True
  394.  
  395.         # Semi-Private
  396.         self.Original = None
  397.         self.Parents = []
  398.  
  399.         # Private Stuff
  400.         self.Allocated = False
  401.         self.Add = False
  402.         self.MatchedInclude = False
  403.  
  404.         # Caching
  405.         self.Categories = self.DesktopEntry.getCategories()
  406.  
  407.     def save(self):
  408.         if self.DesktopEntry.tainted == True:
  409.             self.DesktopEntry.write()
  410.  
  411.     def getDir(self):
  412.         return self.DesktopEntry.filename.replace(self.Filename, '')
  413.  
  414.     def getType(self):
  415.         # Can be one of System/User/Both
  416.         if xdg.Config.root_mode == False:
  417.             if self.Original:
  418.                 return "Both"
  419.             elif xdg_data_dirs[0] in self.DesktopEntry.filename:
  420.                 return "User"
  421.             else:
  422.                 return "System"
  423.         else:
  424.             return "User"
  425.  
  426.     def setAttributes(self, filename, dir="", prefix=""):
  427.         self.Filename = filename
  428.         self.Prefix = prefix
  429.         self.DesktopFileID = os.path.join(prefix,filename).replace("/", "-")
  430.  
  431.         if not os.path.isabs(self.DesktopEntry.filename):
  432.             self.__setFilename()
  433.  
  434.     def updateAttributes(self):
  435.         if self.getType() == "System":
  436.             self.Original = MenuEntry(self.Filename, self.getDir(), self.Prefix)
  437.             self.__setFilename()
  438.  
  439.     def __setFilename(self):
  440.         if xdg.Config.root_mode == False:
  441.             path = xdg_data_dirs[0]
  442.         else:
  443.             path= xdg_data_dirs[1]
  444.  
  445.         if self.DesktopEntry.getType() == "Application":
  446.             dir = os.path.join(path, "applications")
  447.         else:
  448.             dir = os.path.join(path, "desktop-directories")
  449.  
  450.         self.DesktopEntry.filename = os.path.join(dir, self.Filename)
  451.  
  452.     def __cmp__(self, other):
  453.         return locale.strcoll(self.DesktopEntry.getName(), other.DesktopEntry.getName())
  454.  
  455.     def __eq__(self, other):
  456.         if self.DesktopFileID == str(other):
  457.             return True
  458.         else:
  459.             return False
  460.  
  461.     def __repr__(self):
  462.         return self.DesktopFileID
  463.  
  464.  
  465. class Separator:
  466.     "Just a dummy class for Separators"
  467.     def __init__(self, parent):
  468.         self.Parent = parent
  469.         self.Show = True
  470.  
  471.  
  472. class Header:
  473.     "Class for Inline Headers"
  474.     def __init__(self, name, generic_name, comment):
  475.         self.Name = name
  476.         self.GenericName = generic_name
  477.         self.Comment = comment
  478.  
  479.     def __str__(self):
  480.         return self.Name
  481.  
  482.  
  483. tmp = {}
  484.  
  485. def __getFileName(filename):
  486.     dirs = xdg_config_dirs[:]
  487.     if xdg.Config.root_mode == True:
  488.         dirs.pop(0)
  489.  
  490.     for dir in dirs:
  491.         menuname = os.path.join (dir, "menus" , filename)
  492.         if os.path.isdir(dir) and os.path.isfile(menuname):
  493.             return menuname
  494.  
  495. def parse(filename=None):
  496.     # conver to absolute path
  497.     if filename and not os.path.isabs(filename):
  498.         filename = __getFileName(filename)
  499.  
  500.     # use default if no filename given
  501.     if not filename: 
  502.         candidate = os.environ.get('XDG_MENU_PREFIX', '') + "applications.menu"
  503.         filename = __getFileName(candidate)
  504.         
  505.     if not filename:
  506.         raise ParsingError('File not found', "/etc/xdg/menus/%s" % candidate)
  507.  
  508.     # check if it is a .menu file
  509.     if not os.path.splitext(filename)[1] == ".menu":
  510.         raise ParsingError('Not a .menu file', filename)
  511.  
  512.     # create xml parser
  513.     try:
  514.         doc = xml.dom.minidom.parse(filename)
  515.     except xml.parsers.expat.ExpatError:
  516.         raise ParsingError('Not a valid .menu file', filename)
  517.  
  518.     # parse menufile
  519.     tmp["Root"] = ""
  520.     tmp["mergeFiles"] = []
  521.     tmp["DirectoryDirs"] = []
  522.     tmp["cache"] = MenuEntryCache()
  523.  
  524.     __parse(doc, filename, tmp["Root"])
  525.     __parsemove(tmp["Root"])
  526.     __postparse(tmp["Root"])
  527.  
  528.     tmp["Root"].Doc = doc
  529.     tmp["Root"].Filename = filename
  530.  
  531.     # generate the menu
  532.     __genmenuNotOnlyAllocated(tmp["Root"])
  533.     __genmenuOnlyAllocated(tmp["Root"])
  534.  
  535.     # and finally sort
  536.     sort(tmp["Root"])
  537.  
  538.     return tmp["Root"]
  539.  
  540.  
  541. def __parse(node, filename, parent=None):
  542.     for child in node.childNodes:
  543.         if child.nodeType == ELEMENT_NODE:
  544.             if child.tagName == 'Menu':
  545.                 __parseMenu(child, filename, parent)
  546.             elif child.tagName == 'AppDir':
  547.                 try:
  548.                     __parseAppDir(child.childNodes[0].nodeValue, filename, parent)
  549.                 except IndexError:
  550.                     raise ValidationError('AppDir cannot be empty', filename)
  551.             elif child.tagName == 'DefaultAppDirs':
  552.                 __parseDefaultAppDir(filename, parent)
  553.             elif child.tagName == 'DirectoryDir':
  554.                 try:
  555.                     __parseDirectoryDir(child.childNodes[0].nodeValue, filename, parent)
  556.                 except IndexError:
  557.                     raise ValidationError('DirectoryDir cannot be empty', filename)
  558.             elif child.tagName == 'DefaultDirectoryDirs':
  559.                 __parseDefaultDirectoryDir(filename, parent)
  560.             elif child.tagName == 'Name' :
  561.                 try:
  562.                     parent.Name = child.childNodes[0].nodeValue
  563.                 except IndexError:
  564.                     raise ValidationError('Name cannot be empty', filename)
  565.             elif child.tagName == 'Directory' :
  566.                 try:
  567.                     parent.Directories.append(child.childNodes[0].nodeValue)
  568.                 except IndexError:
  569.                     raise ValidationError('Directory cannot be empty', filename)
  570.             elif child.tagName == 'OnlyUnallocated':
  571.                 parent.OnlyUnallocated = True
  572.             elif child.tagName == 'NotOnlyUnallocated':
  573.                 parent.OnlyUnallocated = False
  574.             elif child.tagName == 'Deleted':
  575.                 parent.Deleted = True
  576.             elif child.tagName == 'NotDeleted':
  577.                 parent.Deleted = False
  578.             elif child.tagName == 'Include' or child.tagName == 'Exclude':
  579.                 parent.Rules.append(Rule(child.tagName, child))
  580.             elif child.tagName == 'MergeFile':
  581.                 try:
  582.                     if child.getAttribute("type") == "parent":
  583.                         __parseMergeFile("applications.menu", child, filename, parent)
  584.                     else:
  585.                         __parseMergeFile(child.childNodes[0].nodeValue, child, filename, parent)
  586.                 except IndexError:
  587.                     raise ValidationError('MergeFile cannot be empty', filename)
  588.             elif child.tagName == 'MergeDir':
  589.                 try:
  590.                     __parseMergeDir(child.childNodes[0].nodeValue, child, filename, parent)
  591.                 except IndexError:
  592.                     raise ValidationError('MergeDir cannot be empty', filename)
  593.             elif child.tagName == 'DefaultMergeDirs':
  594.                 __parseDefaultMergeDirs(child, filename, parent)
  595.             elif child.tagName == 'Move':
  596.                 parent.Moves.append(Move(child))
  597.             elif child.tagName == 'Layout':
  598.                 if len(child.childNodes) > 1:
  599.                     parent.Layout = Layout(child)
  600.             elif child.tagName == 'DefaultLayout':
  601.                 if len(child.childNodes) > 1:
  602.                     parent.DefaultLayout = Layout(child)
  603.             elif child.tagName == 'LegacyDir':
  604.                 try:
  605.                     __parseLegacyDir(child.childNodes[0].nodeValue, child.getAttribute("prefix"), filename, parent)
  606.                 except IndexError:
  607.                     raise ValidationError('LegacyDir cannot be empty', filename)
  608.             elif child.tagName == 'KDELegacyDirs':
  609.                 __parseKDELegacyDirs(filename, parent)
  610.  
  611. def __parsemove(menu):
  612.     for submenu in menu.Submenus:
  613.         __parsemove(submenu)
  614.  
  615.     # parse move operations
  616.     for move in menu.Moves:
  617.         move_from_menu = menu.getMenu(move.Old)
  618.         if move_from_menu:
  619.             move_to_menu = menu.getMenu(move.New)
  620.  
  621.             menus = move.New.split("/")
  622.             oldparent = None
  623.             while len(menus) > 0:
  624.                 if not oldparent:
  625.                     oldparent = menu
  626.                 newmenu = oldparent.getMenu(menus[0])
  627.                 if not newmenu:
  628.                     newmenu = Menu()
  629.                     newmenu.Name = menus[0]
  630.                     if len(menus) > 1:
  631.                         newmenu.NotInXml = True
  632.                     oldparent.addSubmenu(newmenu)
  633.                 oldparent = newmenu
  634.                 menus.pop(0)
  635.  
  636.             newmenu += move_from_menu
  637.             move_from_menu.Parent.Submenus.remove(move_from_menu)
  638.  
  639. def __postparse(menu):
  640.     # unallocated / deleted
  641.     if menu.Deleted == "notset":
  642.         menu.Deleted = False
  643.     if menu.OnlyUnallocated == "notset":
  644.         menu.OnlyUnallocated = False
  645.  
  646.     # Layout Tags
  647.     if not menu.Layout or not menu.DefaultLayout:
  648.         if menu.DefaultLayout:
  649.             menu.Layout = menu.DefaultLayout
  650.         elif menu.Layout:
  651.             if menu.Depth > 0:
  652.                 menu.DefaultLayout = menu.Parent.DefaultLayout
  653.             else:
  654.                 menu.DefaultLayout = Layout()
  655.         else:
  656.             if menu.Depth > 0:
  657.                 menu.Layout = menu.Parent.DefaultLayout
  658.                 menu.DefaultLayout = menu.Parent.DefaultLayout
  659.             else:
  660.                 menu.Layout = Layout()
  661.                 menu.DefaultLayout = Layout()
  662.  
  663.     # add parent's app/directory dirs
  664.     if menu.Depth > 0:
  665.         menu.AppDirs = menu.Parent.AppDirs + menu.AppDirs
  666.         menu.DirectoryDirs = menu.Parent.DirectoryDirs + menu.DirectoryDirs
  667.  
  668.     # remove duplicates
  669.     menu.Directories = __removeDuplicates(menu.Directories)
  670.     menu.DirectoryDirs = __removeDuplicates(menu.DirectoryDirs)
  671.     menu.AppDirs = __removeDuplicates(menu.AppDirs)
  672.  
  673.     # go recursive through all menus
  674.     for submenu in menu.Submenus:
  675.         __postparse(submenu)
  676.  
  677.     # reverse so handling is easier
  678.     menu.Directories.reverse()
  679.     menu.DirectoryDirs.reverse()
  680.     menu.AppDirs.reverse()
  681.  
  682.     # get the valid .directory file out of the list
  683.     for directory in menu.Directories:
  684.         for dir in menu.DirectoryDirs:
  685.             if os.path.isfile(os.path.join(dir, directory)):
  686.                 menuentry = MenuEntry(directory, dir)
  687.                 if not menu.Directory:
  688.                     menu.Directory = menuentry
  689.                 elif menuentry.getType() == "System":
  690.                     if menu.Directory.getType() == "User":
  691.                         menu.Directory.Original = menuentry
  692.         if menu.Directory:
  693.             break
  694.  
  695.  
  696. # Menu parsing stuff
  697. def __parseMenu(child, filename, parent):
  698.     m = Menu()
  699.     __parse(child, filename, m)
  700.     if parent:
  701.         parent.addSubmenu(m)
  702.     else:
  703.         tmp["Root"] = m
  704.  
  705. # helper function
  706. def __check(value, filename, type):
  707.     path = os.path.dirname(filename)
  708.  
  709.     if not os.path.isabs(value):
  710.         value = os.path.join(path, value)
  711.  
  712.     value = os.path.abspath(value)
  713.  
  714.     if type == "dir" and os.path.exists(value) and os.path.isdir(value):
  715.         return value
  716.     elif type == "file" and os.path.exists(value) and os.path.isfile(value):
  717.         return value
  718.     else:
  719.         return False
  720.  
  721. # App/Directory Dir Stuff
  722. def __parseAppDir(value, filename, parent):
  723.     value = __check(value, filename, "dir")
  724.     if value:
  725.         parent.AppDirs.append(value)
  726.  
  727. def __parseDefaultAppDir(filename, parent):
  728.     for dir in reversed(xdg_data_dirs):
  729.         __parseAppDir(os.path.join(dir, "applications"), filename, parent)
  730.  
  731. def __parseDirectoryDir(value, filename, parent):
  732.     value = __check(value, filename, "dir")
  733.     if value:
  734.         parent.DirectoryDirs.append(value)
  735.  
  736. def __parseDefaultDirectoryDir(filename, parent):
  737.     for dir in reversed(xdg_data_dirs):
  738.         __parseDirectoryDir(os.path.join(dir, "desktop-directories"), filename, parent)
  739.  
  740. # Merge Stuff
  741. def __parseMergeFile(value, child, filename, parent):
  742.     if child.getAttribute("type") == "parent":
  743.         for dir in xdg_config_dirs:
  744.             rel_file = filename.replace(dir, "").strip("/")
  745.             if rel_file != filename:
  746.                 for p in xdg_config_dirs:
  747.                     if dir == p:
  748.                         continue
  749.                     if os.path.isfile(os.path.join(p,rel_file)):
  750.                         __mergeFile(os.path.join(p,rel_file),child,parent)
  751.                         break
  752.     else:
  753.         value = __check(value, filename, "file")
  754.         if value:
  755.             __mergeFile(value, child, parent)
  756.  
  757. def __parseMergeDir(value, child, filename, parent):
  758.     value = __check(value, filename, "dir")
  759.     if value:
  760.         for item in os.listdir(value):
  761.             try:
  762.                 if os.path.splitext(item)[1] == ".menu":
  763.                     __mergeFile(os.path.join(value, item), child, parent)
  764.             except UnicodeDecodeError:
  765.                 continue
  766.  
  767. def __parseDefaultMergeDirs(child, filename, parent):
  768.     basename = os.path.splitext(os.path.basename(filename))[0]
  769.     for dir in reversed(xdg_config_dirs):
  770.         __parseMergeDir(os.path.join(dir, "menus", basename + "-merged"), child, filename, parent)
  771.  
  772. def __mergeFile(filename, child, parent):
  773.     # check for infinite loops
  774.     if filename in tmp["mergeFiles"]:
  775.         if debug:
  776.             raise ParsingError('Infinite MergeFile loop detected', filename)
  777.         else:
  778.             return
  779.  
  780.     tmp["mergeFiles"].append(filename)
  781.  
  782.     # load file
  783.     try:
  784.         doc = xml.dom.minidom.parse(filename)
  785.     except IOError:
  786.         if debug:
  787.             raise ParsingError('File not found', filename)
  788.         else:
  789.             return
  790.     except xml.parsers.expat.ExpatError:
  791.         if debug:
  792.             raise ParsingError('Not a valid .menu file', filename)
  793.         else:
  794.             return
  795.  
  796.     # append file
  797.     for child in doc.childNodes:
  798.         if child.nodeType == ELEMENT_NODE:
  799.             __parse(child,filename,parent)
  800.             break
  801.  
  802. # Legacy Dir Stuff
  803. def __parseLegacyDir(dir, prefix, filename, parent):
  804.     m = __mergeLegacyDir(dir,prefix,filename,parent)
  805.     if m:
  806.         parent += m
  807.  
  808. def __mergeLegacyDir(dir, prefix, filename, parent):
  809.     dir = __check(dir,filename,"dir")
  810.     if dir and dir not in tmp["DirectoryDirs"]:
  811.         tmp["DirectoryDirs"].append(dir)
  812.  
  813.         m = Menu()
  814.         m.AppDirs.append(dir)
  815.         m.DirectoryDirs.append(dir)
  816.         m.Name = os.path.basename(dir)
  817.         m.NotInXml = True
  818.  
  819.         for item in os.listdir(dir):
  820.             try:
  821.                 if item == ".directory":
  822.                     m.Directories.append(item)
  823.                 elif os.path.isdir(os.path.join(dir,item)):
  824.                     m.addSubmenu(__mergeLegacyDir(os.path.join(dir,item), prefix, filename, parent))
  825.             except UnicodeDecodeError:
  826.                 continue
  827.  
  828.         tmp["cache"].addMenuEntries([dir],prefix, True)
  829.         menuentries = tmp["cache"].getMenuEntries([dir], False)
  830.  
  831.         for menuentry in menuentries:
  832.             categories = menuentry.Categories
  833.             if len(categories) == 0:
  834.                 r = Rule("Include")
  835.                 r.parseFilename(menuentry.DesktopFileID)
  836.                 r.compile()
  837.                 m.Rules.append(r)
  838.             if not dir in parent.AppDirs:
  839.                 categories.append("Legacy")
  840.                 menuentry.Categories = categories
  841.  
  842.         return m
  843.  
  844. def __parseKDELegacyDirs(filename, parent):
  845.     f=os.popen3("kde-config --path apps")
  846.     output = f[1].readlines()
  847.     try:
  848.         for dir in output[0].split(":"):
  849.             __parseLegacyDir(dir,"kde", filename, parent)
  850.     except IndexError:
  851.         pass
  852.  
  853. # remove duplicate entries from a list
  854. def __removeDuplicates(list):
  855.     set = {}
  856.     list.reverse()
  857.     list = [set.setdefault(e,e) for e in list if e not in set]
  858.     list.reverse()
  859.     return list
  860.  
  861. # Finally generate the menu
  862. def __genmenuNotOnlyAllocated(menu):
  863.     for submenu in menu.Submenus:
  864.         __genmenuNotOnlyAllocated(submenu)
  865.  
  866.     if menu.OnlyUnallocated == False:
  867.         tmp["cache"].addMenuEntries(menu.AppDirs)
  868.         menuentries = []
  869.         for rule in menu.Rules:
  870.             menuentries = rule.do(tmp["cache"].getMenuEntries(menu.AppDirs), rule.Type, 1)
  871.         for menuentry in menuentries:
  872.             if menuentry.Add == True:
  873.                 menuentry.Parents.append(menu)
  874.                 menuentry.Add = False
  875.                 menuentry.Allocated = True
  876.                 menu.MenuEntries.append(menuentry)
  877.  
  878. def __genmenuOnlyAllocated(menu):
  879.     for submenu in menu.Submenus:
  880.         __genmenuOnlyAllocated(submenu)
  881.  
  882.     if menu.OnlyUnallocated == True:
  883.         tmp["cache"].addMenuEntries(menu.AppDirs)
  884.         menuentries = []
  885.         for rule in menu.Rules:
  886.             menuentries = rule.do(tmp["cache"].getMenuEntries(menu.AppDirs), rule.Type, 2)
  887.         for menuentry in menuentries:
  888.             if menuentry.Add == True:
  889.                 menuentry.Parents.append(menu)
  890.             #   menuentry.Add = False
  891.             #   menuentry.Allocated = True
  892.                 menu.MenuEntries.append(menuentry)
  893.  
  894. # And sorting ...
  895. def sort(menu):
  896.     menu.Entries = []
  897.     menu.Visible = 0
  898.  
  899.     for submenu in menu.Submenus:
  900.         sort(submenu)
  901.  
  902.     tmp_s = []
  903.     tmp_e = []
  904.  
  905.     for order in menu.Layout.order:
  906.         if order[0] == "Filename":
  907.             tmp_e.append(order[1])
  908.         elif order[0] == "Menuname":
  909.             tmp_s.append(order[1])
  910.     
  911.     for order in menu.Layout.order:
  912.         if order[0] == "Separator":
  913.             separator = Separator(menu)
  914.             if len(menu.Entries) > 0 and isinstance(menu.Entries[-1], Separator):
  915.                 separator.Show = False
  916.             menu.Entries.append(separator)
  917.         elif order[0] == "Filename":
  918.             menuentry = menu.getMenuEntry(order[1])
  919.             if menuentry:
  920.                 menu.Entries.append(menuentry)
  921.         elif order[0] == "Menuname":
  922.             submenu = menu.getMenu(order[1])
  923.             if submenu:
  924.                 __parse_inline(submenu, menu)
  925.         elif order[0] == "Merge":
  926.             if order[1] == "files" or order[1] == "all":
  927.                 menu.MenuEntries.sort()
  928.                 for menuentry in menu.MenuEntries:
  929.                     if menuentry not in tmp_e:
  930.                         menu.Entries.append(menuentry)
  931.             elif order[1] == "menus" or order[1] == "all":
  932.                 menu.Submenus.sort()
  933.                 for submenu in menu.Submenus:
  934.                     if submenu.Name not in tmp_s:
  935.                         __parse_inline(submenu, menu)
  936.  
  937.     # getHidden / NoDisplay / OnlyShowIn / NotOnlyShowIn / Deleted / NoExec
  938.     for entry in menu.Entries:
  939.         entry.Show = True
  940.         menu.Visible += 1
  941.         if isinstance(entry, Menu):
  942.             if entry.Deleted == True:
  943.                 entry.Show = "Deleted"
  944.                 menu.Visible -= 1
  945.             elif isinstance(entry.Directory, MenuEntry):
  946.                 if entry.Directory.DesktopEntry.getNoDisplay() == True:
  947.                     entry.Show = "NoDisplay"
  948.                     menu.Visible -= 1
  949.                 elif entry.Directory.DesktopEntry.getHidden() == True:
  950.                     entry.Show = "Hidden"
  951.                     menu.Visible -= 1
  952.         elif isinstance(entry, MenuEntry):
  953.             if entry.DesktopEntry.getNoDisplay() == True:
  954.                 entry.Show = "NoDisplay"
  955.                 menu.Visible -= 1
  956.             elif entry.DesktopEntry.getHidden() == True:
  957.                 entry.Show = "Hidden"
  958.                 menu.Visible -= 1
  959.             elif entry.DesktopEntry.getTryExec() and not __try_exec(entry.DesktopEntry.getTryExec()):
  960.                 entry.Show = "NoExec"
  961.                 menu.Visible -= 1
  962.             elif xdg.Config.windowmanager:
  963.                 if ( entry.DesktopEntry.getOnlyShowIn() != [] and xdg.Config.windowmanager not in entry.DesktopEntry.getOnlyShowIn() ) \
  964.                 or xdg.Config.windowmanager in entry.DesktopEntry.getNotShowIn():
  965.                     entry.Show = "NotShowIn"
  966.                     menu.Visible -= 1
  967.         elif isinstance(entry,Separator):
  968.             menu.Visible -= 1
  969.  
  970.     # remove separators at the beginning and at the end
  971.     if len(menu.Entries) > 0:
  972.         if isinstance(menu.Entries[0], Separator):
  973.             menu.Entries[0].Show = False
  974.     if len(menu.Entries) > 1:
  975.         if isinstance(menu.Entries[-1], Separator):
  976.             menu.Entries[-1].Show = False
  977.  
  978.     # show_empty tag
  979.     for entry in menu.Entries:
  980.         if isinstance(entry,Menu) and entry.Layout.show_empty == "false" and entry.Visible == 0:
  981.             entry.Show = "Empty"
  982.             menu.Visible -= 1
  983.             if entry.NotInXml == True:
  984.                 menu.Entries.remove(entry)
  985.  
  986. def __try_exec(executable):
  987.     paths = os.environ['PATH'].split(os.pathsep)
  988.     if not os.path.isfile(executable):
  989.         for p in paths:
  990.             f = os.path.join(p, executable)
  991.             if os.path.isfile(f):
  992.                 if os.access(f, os.X_OK):
  993.                     return True
  994.     else:
  995.         if os.access(executable, os.X_OK):
  996.             return True
  997.     return False
  998.  
  999. # inline tags
  1000. def __parse_inline(submenu, menu):
  1001.     if submenu.Layout.inline == "true":
  1002.         if len(submenu.Entries) == 1 and submenu.Layout.inline_alias == "true":
  1003.             menuentry = submenu.Entries[0]
  1004.             menuentry.DesktopEntry.set("Name", submenu.getName(), locale = True)
  1005.             menuentry.DesktopEntry.set("GenericName", submenu.getGenericName(), locale = True)
  1006.             menuentry.DesktopEntry.set("Comment", submenu.getComment(), locale = True)
  1007.             menu.Entries.append(menuentry)
  1008.         elif len(submenu.Entries) <= submenu.Layout.inline_limit or submenu.Layout.inline_limit == 0:
  1009.             if submenu.Layout.inline_header == "true":
  1010.                 header = Header(submenu.getName(), submenu.getGenericName(), submenu.getComment())
  1011.                 menu.Entries.append(header)
  1012.             for entry in submenu.Entries:
  1013.                 menu.Entries.append(entry)
  1014.         else:
  1015.             menu.Entries.append(submenu)
  1016.     else:
  1017.         menu.Entries.append(submenu)
  1018.  
  1019. class MenuEntryCache:
  1020.     "Class to cache Desktop Entries"
  1021.     def __init__(self):
  1022.         self.cacheEntries = {}
  1023.         self.cacheEntries['legacy'] = []
  1024.         self.cache = {}
  1025.  
  1026.     def addMenuEntries(self, dirs, prefix="", legacy=False):
  1027.         for dir in dirs:
  1028.             if not self.cacheEntries.has_key(dir):
  1029.                 self.cacheEntries[dir] = []
  1030.                 self.__addFiles(dir, "", prefix, legacy)
  1031.  
  1032.     def __addFiles(self, dir, subdir, prefix, legacy):
  1033.         for item in os.listdir(os.path.join(dir,subdir)):
  1034.             if os.path.splitext(item)[1] == ".desktop":
  1035.                 try:
  1036.                     menuentry = MenuEntry(os.path.join(subdir,item), dir, prefix)
  1037.                 except ParsingError:
  1038.                     continue
  1039.  
  1040.                 self.cacheEntries[dir].append(menuentry)
  1041.                 if legacy == True:
  1042.                     self.cacheEntries['legacy'].append(menuentry)
  1043.             elif os.path.isdir(os.path.join(dir,subdir,item)) and legacy == False:
  1044.                 self.__addFiles(dir, os.path.join(subdir,item), prefix, legacy)
  1045.  
  1046.     def getMenuEntries(self, dirs, legacy=True):
  1047.         list = []
  1048.         ids = []
  1049.         # handle legacy items
  1050.         appdirs = dirs[:]
  1051.         if legacy == True:
  1052.             appdirs.append("legacy")
  1053.         # cache the results again
  1054.         key = "".join(appdirs)
  1055.         try:
  1056.             return self.cache[key]
  1057.         except KeyError:
  1058.             pass
  1059.         for dir in appdirs:
  1060.             for menuentry in self.cacheEntries[dir]:
  1061.                 try:
  1062.                     if menuentry.DesktopFileID not in ids:
  1063.                         ids.append(menuentry.DesktopFileID)
  1064.                         list.append(menuentry)
  1065.                     elif menuentry.getType() == "System":
  1066.                     # FIXME: This is only 99% correct, but still...
  1067.                         i = list.index(menuentry)
  1068.                         e = list[i]
  1069.                         if e.getType() == "User":
  1070.                             e.Original = menuentry
  1071.                 except UnicodeDecodeError:
  1072.                     continue
  1073.         self.cache[key] = list
  1074.         return list
  1075.